/* async.h
   Mathieu Stefani, 05 novembre 2015

  This header brings a Promise<T> class inspired by the Promises/A+
  specification for asynchronous operations
*/

#pragma once

#include <pistache/typeid.h>

#include <type_traits>
#include <functional>
#include <memory>
#include <atomic>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <stdexcept>
#include <typeinfo>


namespace Pistache {
namespace Async {

    class Error : public std::runtime_error {
    public:
        explicit Error(const char* what) : std::runtime_error(what) { }
        explicit Error(const std::string& what) : std::runtime_error(what) { }
    };

    class BadType : public Error {
    public:
        BadType(TypeId id)
            : Error("Argument type can not be used to resolve the promise "
                  " (TypeId does not match)")
            , id_(std::move(id))
        { }

      TypeId typeId() const {
          return id_;
      }

    private:
        TypeId id_;
    };

    class BadAnyCast : public std::bad_cast {
    public:
        virtual const char* what() const noexcept { return "Bad any cast"; }
        virtual ~BadAnyCast() { }
    };

    enum class State {
        Pending, Fulfilled, Rejected
    };

    template<typename T> class Promise;

    class PromiseBase {
    public:
        virtual ~PromiseBase() { }
        virtual bool isPending() const = 0;
        virtual bool isFulfilled() const = 0;
        virtual bool isRejected() const = 0;

        bool isSettled() const { return isFulfilled() || isRejected(); }
    };

    namespace detail {
        template<typename Func, typename T>
        struct IsCallable {

            template<typename U>
            static auto test(U *) -> decltype(std::declval<Func>()(std::declval<U>()), std::true_type());

            template<typename U>
            static auto test(...) -> std::false_type;

            static constexpr bool value = std::is_same<decltype(test<T>(0)), std::true_type>::value;
        };

        template<typename Func>
        struct IsMoveCallable : public IsMoveCallable<decltype(&Func::operator())> { };

        template<typename R, typename Class, typename Arg>
        struct IsMoveCallable<R (Class::*)(Arg) const> : public std::is_rvalue_reference<Arg> { };

        template<typename Func, typename Arg>
        typename std::conditional<
            IsMoveCallable<Func>::value,
            Arg&&,
            const Arg&
        >::type tryMove(Arg& arg) {
            return std::move(arg);
        }

        template<typename Func>
        struct FunctionTrait : public FunctionTrait<decltype(&Func::operator())> { };

        template<typename R, typename Class, typename... Args>
        struct FunctionTrait<R (Class::*)(Args...) const> {
            typedef R ReturnType;

            static constexpr size_t ArgsCount = sizeof...(Args);
        };

        template<typename R, typename Class, typename... Args>
        struct FunctionTrait<R (Class::*)(Args...)> {
            typedef R ReturnType;

            static constexpr size_t ArgsCount = sizeof...(Args);
        };

        template<typename T>
        struct RemovePromise {
            typedef T Type;
        };

        template<typename T>
        struct RemovePromise<Promise<T>> {
            typedef T Type;
        };

        template<size_t N, typename... T> struct nth_element;

        template<typename Head, typename... Tail>
        struct nth_element<0, Head, Tail...> {
            typedef Head type;
        };

        template<size_t N, typename Head, typename... Tail>
        struct nth_element<N, Head, Tail...> {
            typedef typename nth_element<N - 1, Tail...>::type type;
        };

    }

    namespace Private {

        struct InternalRethrow {
            InternalRethrow(std::exception_ptr _exc)
                : exc(std::move(_exc))
            { }

            std::exception_ptr exc;
        };

        struct IgnoreException {
            void operator()(std::exception_ptr) const { }
        };

        struct NoExcept {
            void operator()(std::exception_ptr) const { std::terminate(); }
        };

        struct Throw {
            void operator()(std::exception_ptr exc) const {
                throw InternalRethrow(std::move(exc));
            }
        };

        struct Core;

        class Request {
        public:
            virtual void resolve(const std::shared_ptr<Core>& core) = 0;
            virtual void reject(const std::shared_ptr<Core>& core) = 0;
            virtual ~Request() {}
        };

        struct Core {
            Core(State _state, TypeId _id)
                : allocated(false)
                , state(_state)
                , exc()
                , mtx()
                , requests()
                , id(_id)
            { }

            bool allocated;
            std::atomic<State> state;
            std::exception_ptr exc;

            /*
             * We need this lock because a Promise might be resolved or rejected from a thread A
             * while a continuation to the same Promise (Core) might be attached at the same from
             * a thread B. If that's the case, then we need to serialize operations so that we
             * avoid a race-condition.
             *
             * Since we have a lock, we have a blocking progress guarantee but I don't expect this
             * to be a major bottleneck as I don't expect major contention on the lock
             * If it ends up being a bottlenick, try @improving it by experimenting with a lock-free
             * scheme
             */
            std::mutex mtx;
            std::vector<std::shared_ptr<Request>> requests;
            TypeId id;

            virtual void* memory() = 0;

            virtual bool isVoid() const = 0;

            template<typename T, typename... Args>
            void construct(Args&&... args) {
                if (isVoid())
                    throw Error("Can not construct a void core");

                if (id != TypeId::of<T>()) {
                    throw BadType(id);
                }

                void *mem = memory();

                if (allocated) {
                    reinterpret_cast<T*>(mem)->~T();
                    allocated = false;
                }

                new (mem) T(std::forward<Args>(args)...);
                allocated = true;
                state = State::Fulfilled;
            }

            virtual ~Core() {}
        };

        template<typename T>
        struct CoreT : public Core {
            CoreT()
                : Core(State::Pending, TypeId::of<T>())
                , storage()
            { }

            ~CoreT() {
                if (allocated) {
                    reinterpret_cast<T*>(&storage)->~T();
                    allocated = false;
                }
            }
            
            template<class Other>
            struct Rebind {
                typedef CoreT<Other> Type;
            };

            T& value() {
                if (state != State::Fulfilled)
                    throw Error("Attempted to take the value of a not fulfilled promise");

                return *reinterpret_cast<T*>(&storage);
            }

            bool isVoid() const override { return false; }

        protected:
            void *memory() override {
                return &storage;
            }

        private:
            typedef typename std::aligned_storage<sizeof(T), alignof(T)>::type Storage;
            Storage storage;
        };

        template<>
        struct CoreT<void> : public Core {
            CoreT()
                : Core(State::Pending, TypeId::of<void>())
            { }

            bool isVoid() const override { return true; }

        protected:
            void *memory() override {
                return nullptr;
            }
        };

        template<typename T>
        struct Continuable : public Request {
            Continuable(const std::shared_ptr<Core>& chain)
                : resolveCount_(0)
                , rejectCount_(0)
                , chain_(chain)
            { }

            void resolve(const std::shared_ptr<Core>& core) override {
                if (resolveCount_ >= 1)
                    throw Error("Resolve must not be called more than once");

                doResolve(coreCast(core));
                ++resolveCount_;
            }

            void reject(const std::shared_ptr<Core>& core) override {
                if (rejectCount_ >= 1)
                    throw Error("Reject must not be called more than once");

                try {
                    doReject(coreCast(core));
                } catch (const InternalRethrow& e) {
                    chain_->exc = std::move(e.exc);
                    chain_->state = State::Rejected;
                    for (const auto& req: chain_->requests) {
                        req->reject(chain_);
                    }
                }

                ++rejectCount_;
            }

            std::shared_ptr<CoreT<T>> coreCast(const std::shared_ptr<Core>& core) const {
                return std::static_pointer_cast<CoreT<T>>(core);
            }

            virtual void doResolve(const std::shared_ptr<CoreT<T>>& core) = 0;
            virtual void doReject(const std::shared_ptr<CoreT<T>>& core) = 0;

            virtual ~Continuable() { }

            size_t resolveCount_;
            size_t rejectCount_;
            std::shared_ptr<Core> chain_;
        };

        namespace impl {

            template<typename T, typename Resolve, typename Reject, typename U> struct Continuation;

            template<typename T, typename Resolve, typename Reject, typename Res, typename Cls, typename... Args>
            struct Continuation<T, Resolve, Reject, Res (Cls::*)(Args...) const> : public Continuation<T, Resolve, Reject, Res (Args...)> {
                typedef Continuation<T, Resolve, Reject, Res (Args...)> Base;

                Continuation(
                        const std::shared_ptr<Core>& chain,
                        Resolve resolve,
                        Reject reject)
                    : Base(chain, std::move(resolve), std::move(reject))
                { }
            };

            template<typename T, typename Resolve, typename Reject, typename Res, typename Cls, typename... Args>
            struct Continuation<T, Resolve, Reject, Res (Cls::*)(Args...)> : public Continuation<T, Resolve, Reject, Res (Args...)> {
                typedef Continuation<T, Resolve, Reject, Res (Args...)> Base;

                Continuation(
                        const std::shared_ptr<Core>& chain,
                        Resolve resolve,
                        Reject reject)
                    : Base(chain, std::move(resolve), std::move(reject))
                { }
            };

            // General specialization
            template<typename T, typename Resolve, typename Reject, typename Res, typename... Args>
            struct Continuation<T, Resolve, Reject, Res (Args...)> : public Continuable<T> {

                static_assert(sizeof...(Args) == 1,
                        "A continuation should only take one single argument");

                typedef typename detail::nth_element<0, Args...>::type Arg;

                static_assert(
                        std::is_same<T, Arg>::value ||
                        std::is_convertible<T, Arg>::value,
                        "Incompatible types detected");

                Continuation(
                        const std::shared_ptr<Core>& chain,
                        Resolve resolve,
                        Reject reject)
                    : Continuable<T>(chain)
                    , resolve_(resolve)
                    , reject_(reject)
                { }

                void doResolve(const std::shared_ptr<CoreT<T>>& core) {
                    finishResolve(resolve_(detail::tryMove<Resolve>(core->value())));
                }

                void doReject(const std::shared_ptr<CoreT<T>>& core) {
                    reject_(core->exc);
                    for (const auto& req: this->chain_->requests) {
                        req->reject(this->chain_);
                    }
                }

                template<typename Ret>
                void finishResolve(Ret&& ret) const {
                    typedef typename std::decay<Ret>::type CleanRet;
                    this->chain_->template construct<CleanRet>(std::forward<Ret>(ret));
                    for (const auto& req: this->chain_->requests) {
                        req->resolve(this->chain_);
                    }
                }

                Resolve resolve_;
                Reject reject_;
            };

            // Specialization for a void-Promise
            template<typename Resolve, typename Reject, typename Res, typename... Args>
            struct Continuation<void, Resolve, Reject, Res (Args...)> : public Continuable<void> {

                Continuation(
                        const std::shared_ptr<Core>& chain,
                        Resolve resolve,
                        Reject reject)
                    : Continuable<void>(chain)
                    , resolve_(resolve)
                    , reject_(reject)
                { }

                static_assert(sizeof...(Args) == 0,
                        "Can not attach a non-void continuation to a void-Promise");

                void doResolve(const std::shared_ptr<CoreT<void>>& /*core*/) {
                    finishResolve(resolve_());
                }

                void doReject(const std::shared_ptr<CoreT<void>>& core) {
                    reject_(core->exc);
                    for (const auto& req: this->chain_->requests) {
                        req->reject(this->chain_);
                    }
                }

                template<typename Ret>
                void finishResolve(Ret&& ret) const {
                    typedef typename std::remove_reference<Ret>::type CleanRet;
                    this->chain_->template construct<CleanRet>(std::forward<Ret>(ret));
                    for (const auto& req: this->chain_->requests) {
                        req->resolve(this->chain_);
                    }
                }

                Resolve resolve_;
                Reject reject_;
            };

            // Specialization for a callback returning void
            template<typename T, typename Resolve, typename Reject, typename... Args>
            struct Continuation<T, Resolve, Reject, void (Args...)> : public Continuable<T> {

                Continuation(
                        const std::shared_ptr<Core>& chain,
                        Resolve resolve,
                        Reject reject)
                    : Continuable<T>(chain)
                    , resolve_(resolve)
                    , reject_(reject)
                { }

                static_assert(sizeof...(Args) == 1,
                        "A continuation should only take one single argument");

                typedef typename detail::nth_element<0, Args...>::type Arg;

                static_assert(
                        std::is_same<T, Arg>::value ||
                        std::is_convertible<T, Arg>::value,
                        "Incompatible types detected");


                void doResolve(const std::shared_ptr<CoreT<T>>& core) {
                    resolve_(core->value());
                }

                void doReject(const std::shared_ptr<CoreT<T>>& core) {
                    reject_(core->exc);
                }

                Resolve resolve_;
                Reject reject_;
            };


            // Specialization for a void-Promise on a callback returning void
            template<typename Resolve, typename Reject, typename... Args>
            struct Continuation<void, Resolve, Reject, void (Args...)> : public Continuable<void> {

                Continuation(
                        const std::shared_ptr<Core>& chain,
                        Resolve resolve,
                        Reject reject)
                    : Continuable<void>(chain)
                    , resolve_(resolve)
                    , reject_(reject)
                { }

                static_assert(sizeof...(Args) == 0,
                        "Can not attach a non-void continuation to a void-Promise");

                void doResolve(const std::shared_ptr<CoreT<void>>& /*core*/) {
                    resolve_();
                }

                void doReject(const std::shared_ptr<CoreT<void>>& core) {
                    reject_(core->exc);
                }

                Resolve resolve_;
                Reject reject_;

            };

            // Specialization for a callback returning a Promise
            template<typename T, typename Resolve, typename Reject, typename U, typename... Args>
            struct Continuation<T, Resolve, Reject, Promise<U> (Args...)> : public Continuable<T> {

                static_assert(sizeof...(Args) == 1,
                        "A continuation should only take one single argument");

                typedef typename detail::nth_element<0, Args...>::type Arg;

                static_assert(
                        std::is_same<T, Arg>::value ||
                        std::is_convertible<T, Arg>::value,
                        "Incompatible types detected");

                Continuation(
                        const std::shared_ptr<Core>& chain,
                        Resolve resolve,
                        Reject reject)
                    : Continuable<T>(chain)
                    , resolve_(resolve)
                    , reject_(reject)
                { }

                void doResolve(const std::shared_ptr<CoreT<T>>& core) {
                    auto promise = resolve_(detail::tryMove<Resolve>(core->value()));
                    finishResolve(promise);
                }

                void doReject(const std::shared_ptr<CoreT<T>>& core) {
                    reject_(core->exc);
                    for (const auto& req: core->requests) {
                        req->reject(core);
                    }
                }

                template<typename PromiseType>
                struct Chainer {
                    Chainer(const std::shared_ptr<Private::Core>& core)
                        : chainCore(core)
                    { }

                    void operator()(const PromiseType& val) {
                        chainCore->construct<PromiseType>(val);
                        for (const auto& req: chainCore->requests) {
                            req->resolve(chainCore);
                        }
                    }

                    std::shared_ptr<Core> chainCore;
                };

                template<
                    typename Promise,
                    typename Type = typename detail::RemovePromise<Promise>::Type>
                Chainer<Type>
                makeChainer(const Promise&) {
                    return Chainer<Type>(this->chain_);
                }

                template<typename P>
                void finishResolve(P& promise) {
                    auto chainer = makeChainer(promise);
                    promise.then(std::move(chainer), [=](std::exception_ptr exc) {
                        auto core = this->chain_;
                        core->exc = std::move(exc);
                        core->state = State::Rejected;

                        for (const auto& req: core->requests) {
                            req->reject(core);
                        }
                    });
                }

                Resolve resolve_;
                Reject reject_;

            };

            // Specialization for a void callback returning a Promise
            template<typename Resolve, typename Reject, typename U, typename... Args>
            struct Continuation<void, Resolve, Reject, Promise<U> (Args...)> : public Continuable<void> {

                static_assert(sizeof...(Args) == 0,
                        "Can not attach a non-void continuation to a void-Promise");

                Continuation(
                        const std::shared_ptr<Core>& chain,
                        Resolve resolve,
                        Reject reject)
                    : Continuable<void>(chain)
                    , resolve_(resolve)
                    , reject_(reject)
                { }

                void doResolve(const std::shared_ptr<CoreT<void>>& /*core*/) {
                    auto promise = resolve_();
                    finishResolve(promise);
                }

                void doReject(const std::shared_ptr<CoreT<void>>& core) {
                    reject_(core->exc);
                    for (const auto& req: core->requests) {
                        req->reject(core);
                    }
                }

                template<typename PromiseType, typename Dummy = void>
                struct Chainer {
                    Chainer(const std::shared_ptr<Private::Core>& core)
                        : chainCore(core)
                    { }

                    void operator()(const PromiseType& val) {
                        chainCore->construct<PromiseType>(val);
                        for (const auto& req: chainCore->requests) {
                            req->resolve(chainCore);
                        }
                    }

                    std::shared_ptr<Core> chainCore;
                };

                template<typename Dummy>
                struct Chainer<void, Dummy> {
                    Chainer(const std::shared_ptr<Private::Core>& core)
                        : chainCore(core)
                    { }

                    void operator()() {
                        auto core = this->chain_;
                        core->state = State::Fulfilled;

                        for (const auto& req: chainCore->requests) {
                            req->resolve(chainCore);
                        }
                    }

                    std::shared_ptr<Core> chainCore;
                };

                template<
                    typename Promise,
                    typename Type = typename detail::RemovePromise<Promise>::Type>
                Chainer<Type>
                makeChainer(const Promise&) {
                    return Chainer<Type>(this->chain_);
                }

                template<typename P>
                void finishResolve(P& promise) {
                    auto chainer = makeChainer(promise);
                    promise.then(std::move(chainer), [=](std::exception_ptr exc) {
                        auto core = this->chain_;
                        core->exc = std::move(exc);
                        core->state = State::Rejected;

                        for (const auto& req: core->requests) {
                            req->reject(core);
                        }
                    });
                }

                Resolve resolve_;
                Reject reject_;

            };

        } // namespace impl

        template<typename T, typename Resolve, typename Reject, typename Sig>
        struct Continuation : public impl::Continuation<T, Resolve, Reject, decltype(&Sig::operator())>
        {

            typedef impl::Continuation<T, Resolve, Reject, decltype(&Sig::operator())> Base;

            Continuation(
                    const std::shared_ptr<Core>& core,
                    Resolve resolve,
                    Reject reject)
                : Base(core, std::move(resolve), std::move(reject))
            { }
        };

        template<typename T, typename Resolve, typename Reject, typename Res, typename... Args>
        struct Continuation<T, Resolve, Reject, Res (*)(Args...)> : public impl::Continuation<T, Resolve, Reject, Res (Args...)>
        {
            typedef impl::Continuation<T, Resolve, Reject, Res (Args...)> Base;

            Continuation(
                    const std::shared_ptr<Core>& core,
                    Resolve resolve,
                    Reject reject)
                : Base(core, std::move(resolve), std::move(reject))
            { }
        };

        template<typename T, typename Resolve, typename Reject, typename Res, typename Cls, typename... Args>
        struct Continuation<T, Resolve, Reject, Res (Cls::*)(Args...)> : public impl::Continuation<T, Resolve, Reject, Res (Args...)>
        {
            typedef impl::Continuation<T, Resolve, Reject, Res (Args...)> Base;

            Continuation(
                    const std::shared_ptr<Core>& core,
                    Resolve resolve,
                    Reject reject)
                : Base(core, std::move(resolve), std::move(reject))
            { }
        };

        template<typename T, typename Resolve, typename Reject, typename Res, typename Cls, typename... Args>
        struct Continuation<T, Resolve, Reject, Res (Cls::*)(Args...) const> : public impl::Continuation<T, Resolve, Reject, Res (Args...)>
        {
            typedef impl::Continuation<T, Resolve, Reject, Res (Args...)> Base;

            Continuation(
                    const std::shared_ptr<Core>& core,
                    Resolve resolve,
                    Reject reject)
                : Base(core, std::move(resolve), std::move(reject))
            { }
        };

        template<typename T, typename Resolve, typename Reject, typename Res, typename... Args>
        struct Continuation<T, Resolve, Reject, std::function<Res (Args...)>> : public impl::Continuation<T, Resolve, Reject, Res (Args...)> {
            typedef impl::Continuation<T, Resolve, Reject, Res (Args...)> Base;

            Continuation(
                    const std::shared_ptr<Core>& core,
                    Resolve resolve,
                    Reject reject)
                : Base(core, std::move(resolve), std::move(reject))
            { }
        };
    }

    class Resolver {
    public:

        Resolver(const std::shared_ptr<Private::Core> &core)
            : core_(core)
        { }

        Resolver(const Resolver& other) = delete;
        Resolver& operator=(const Resolver& other) = delete;

        Resolver(Resolver&& other) = default;
        Resolver& operator=(Resolver&& other) = default;

        template<typename Arg>
        bool operator()(Arg&& arg) const {
            if (!core_) return false;

            typedef typename std::remove_reference<Arg>::type Type;

            if (core_->state != State::Pending)
                throw Error("Attempt to resolve a fulfilled promise");

            /* In a ideal world, this should be checked at compile-time rather
             * than runtime. However, since types are erased, this looks like
             * a difficult task
             */
            if (core_->isVoid()) {
                throw Error("Attempt to resolve a void promise with arguments");
            }

            std::unique_lock<std::mutex> guard(core_->mtx);
            core_->construct<Type>(std::forward<Arg>(arg));

            for (const auto& req: core_->requests) {
                req->resolve(core_);
            }

            return true;
        }

        bool operator()() const {
            if (!core_) return false;

            if (core_->state != State::Pending)
                throw Error("Attempt to resolve a fulfilled promise");

            if (!core_->isVoid())
                throw Error("Attempt ro resolve a non-void promise with no argument");

            std::unique_lock<std::mutex> guard(core_->mtx);
            core_->state = State::Fulfilled;
            for (const auto& req: core_->requests) {
                req->resolve(core_);
            }

            return true;
        }

        void clear() {
            core_ = nullptr;
        }

        Resolver clone() {
            return Resolver(core_);
        }

    private:
        std::shared_ptr<Private::Core> core_;
    };

    class Rejection {
    public:

        Rejection(const std::shared_ptr<Private::Core>& core)
            : core_(core)
        { }

        Rejection(const Rejection& other) = delete;
        Rejection& operator=(const Rejection& other) = delete;

        Rejection(Rejection&& other) = default;
        Rejection& operator=(Rejection&& other) = default;

        template<typename Exc>
        bool operator()(Exc exc) const {
            if (!core_) return false;

            if (core_->state != State::Pending)
                throw Error("Attempt to reject a fulfilled promise");

            std::unique_lock<std::mutex> guard(core_->mtx);
            core_->exc = std::make_exception_ptr(exc);
            core_->state = State::Rejected;
            for (const auto& req: core_->requests) {
                req->reject(core_);
            }

            return true;
        }

        void clear() {
            core_ = nullptr;
        }

        Rejection clone() {
            return Rejection(core_);
        }

    private:
        std::shared_ptr<Private::Core> core_;

    };

    template<typename T>
    class Deferred {
    public:

        Deferred()
            : resolver(nullptr)
            , rejection(nullptr)
        { }

        Deferred(const Deferred& other) = delete;
        Deferred& operator=(const Deferred& other) = delete;

        Deferred(Deferred&& other) = default;
        Deferred& operator=(Deferred&& other) = default;

        Deferred(Resolver _resolver, Rejection _reject)
            : resolver(std::move(_resolver))
            , rejection(std::move(_reject))
        { }

        template<typename U>
        bool resolve(U&& arg) {
            typedef typename std::remove_reference<U>::type CleanU;

            static_assert(std::is_same<T, CleanU>::value || std::is_convertible<U, T>::value,
                    "Types mismatch");

            return resolver(std::forward<U>(arg));
        }

        template<typename... Args>
        void emplaceResolve(Args&& ...) {
        }

        template<typename Exc>
        bool reject(Exc exc) {
            return rejection(std::move(exc));
        }

        void clear() {
            resolver.clear();
            rejection.clear();
        }

    private:
        Resolver resolver;
        Rejection rejection;
    };

    template<>
    class Deferred<void> {
    public:

        Deferred()
            : resolver(nullptr)
            , rejection(nullptr)
        { }

        Deferred(const Deferred& other) = delete;
        Deferred& operator=(const Deferred& other) = delete;

        Deferred(Deferred&& other) = default;
        Deferred& operator=(Deferred&& other) = default;

        Deferred(Resolver _resolver, Rejection _reject)
            : resolver(std::move(_resolver))
            , rejection(std::move(_reject))
        { }

        void resolve() {
            resolver();
        }

        template<typename Exc>
        void reject(Exc _exc) {
            rejection(std::move(_exc));
        }

    private:
        Resolver resolver;
        Rejection rejection;
    };

    static constexpr Private::IgnoreException IgnoreException{};
    static constexpr Private::NoExcept NoExcept{};
    static constexpr Private::Throw Throw{};

    namespace details {

        /*
         * Note that we could use std::result_of to SFINAE-out and dispatch to the right call
         * However, gcc 4.7 does not correctly support std::result_of for SFINAE purposes, so we
         * use a decltype SFINAE-expression instead.
         *
         * See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3462.html and
         * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56283 for reference
         */
        template<typename T, typename Func>
        auto callAsync(Func func, Resolver& resolver, Rejection& rejection)
            -> decltype(std::declval<Func>()(resolver, rejection), void()) {
            func(resolver, rejection);
        }

        template<typename T, typename Func>
        auto callAsync(Func func, Resolver& resolver, Rejection& rejection)
            -> decltype(std::declval<Func>()(Deferred<T>()), void()) {
            func(Deferred<T>(std::move(resolver), std::move(rejection)));
        }
   }

    template<typename T>
    class Promise : public PromiseBase
    {
    public:
        template<typename U> friend class Promise;

        typedef Private::CoreT<T> Core;

        template<typename Func>
        Promise(Func func)
            : core_(std::make_shared<Core>())
            , resolver_(core_)
            , rejection_(core_)
        {
            details::callAsync<T>(func, resolver_, rejection_);
        }

        Promise(const Promise<T>& other) = delete;
        Promise& operator=(const Promise<T>& other) = delete;

        Promise(Promise<T>&& other) = default;
        Promise& operator=(Promise<T>&& other) = default;

        virtual ~Promise()
        {

        }

        template<typename U>
        static
        Promise<T>
        resolved(U&& value) {
            static_assert(!std::is_void<T>::value,
                         "Can not resolve a void promise with parameters");
            static_assert(std::is_same<T, U>::value || std::is_convertible<U, T>::value,
                         "Incompatible value type");

            auto core = std::make_shared<Core>();
            core->template construct<T>(std::forward<U>(value));
            return Promise<T>(std::move(core));
        }

        static
        Promise<void>
        resolved() {
            static_assert(std::is_void<T>::value,
                         "Resolving a non-void promise requires parameters");

            auto core = std::make_shared<Core>();
            core->state = State::Fulfilled;
            return Promise<T>(std::move(core));
        }

        template<typename Exc>
        static Promise<T>
        rejected(Exc exc) {
            auto core = std::make_shared<Core>();
            core->exc = std::make_exception_ptr(exc);
            core->state = State::Rejected;
            return Promise<T>(std::move(core));
        }

        bool isPending() const override { return core_->state == State::Pending; }
        bool isFulfilled() const override { return core_->state == State::Fulfilled; }
        bool isRejected() const override { return core_->state == State::Rejected; }

        template<typename ResolveFunc, typename RejectFunc>
        auto
        then(ResolveFunc resolveFunc, RejectFunc rejectFunc)
            -> Promise<
                typename detail::RemovePromise<
                    typename detail::FunctionTrait<ResolveFunc>::ReturnType
                >::Type
              >
        {

            typedef typename detail::RemovePromise<
                 typename detail::FunctionTrait<ResolveFunc>::ReturnType
            >::Type RetType;

            Promise<RetType> promise;

            typedef Private::Continuation<T, ResolveFunc, RejectFunc, ResolveFunc> Continuation;
            std::shared_ptr<Private::Request> req = std::make_shared<Continuation>(promise.core_, resolveFunc, rejectFunc);

            std::unique_lock<std::mutex> guard(core_->mtx);
            if (isFulfilled()) {
                req->resolve(core_);
            }
            else if (isRejected()) {
                req->reject(core_);
            }

            core_->requests.push_back(req);

            return promise;
        }

    private:
        Promise()
          : core_(std::make_shared<Core>())
          , resolver_(core_)
          , rejection_(core_)
        {
        }

        Promise(std::shared_ptr<Core>&& core)
          : core_(core)
          , resolver_(core_)
          , rejection_(core_)
        { }

        std::shared_ptr<Core> core_;
        Resolver resolver_;
        Rejection rejection_;
    };

    template<typename T>
    class Barrier {
    public:
        Barrier(Promise<T>& promise)
            : promise_(promise) {
        }

        void wait() {
            if (promise_.isFulfilled() || promise_.isRejected()) return;

            promise_.then([&](const T&) mutable {
                std::unique_lock<std::mutex> guard(mtx);
                cv.notify_one();
            }, [&](std::exception_ptr) mutable {
                std::unique_lock<std::mutex> guard(mtx);
                cv.notify_one();
            });

            std::unique_lock<std::mutex> guard(mtx);
            cv.wait(guard, [&] { return promise_.isFulfilled() || promise_.isRejected(); });
        }

        template<class Rep, class Period>
        std::cv_status wait_for(const std::chrono::duration<Rep, Period>& period) {
            if (promise_.isFulfilled() || promise_.isRejected()) return std::cv_status::no_timeout;

            promise_.then([&](const T&) mutable {
                std::unique_lock<std::mutex> guard(mtx);
                cv.notify_one();
            }, [&](std::exception_ptr) mutable {
                std::unique_lock<std::mutex> guard(mtx);
                cv.notify_one();
            });

            std::unique_lock<std::mutex> guard(mtx);
            return cv.wait_for(guard, period);
        }

    private:
        Promise<T>& promise_;
        mutable std::mutex mtx;
        std::condition_variable cv;
    };

    namespace Impl {
        struct Any;
    }

    class Any {
    public:
        friend struct Impl::Any;

        Any(const Any& other) = default;
        Any& operator=(const Any& other) = default;

        Any(Any&& other) = default;
        Any& operator=(Any&& other) = default;

        template<typename T>
        bool is() const {
            return core_->id == TypeId::of<T>();
        }

        template<typename T>
        T cast() const {
            if (!is<T>()) throw BadAnyCast();

            auto core = std::static_pointer_cast<Private::CoreT<T>>(core_);
            return core->value();
        }

    private:
        Any(const std::shared_ptr<Private::Core>& core)
            : core_(core)
        { }
        std::shared_ptr<Private::Core> core_;

    };

    namespace Impl {

        /* Instead of duplicating the code between whenAll and whenAny functions, the main implementation
         * is in the When class below and we configure the class with a policy instead,  depending if we
         * are executing an "all" or "any" operation, how cool is that ?
         */
        struct All {

            struct Data {
                Data(const size_t _total, Resolver _resolver, Rejection _rejection)
                    : total(_total)
                    , resolved(0)
                    , rejected(false)
                    , mtx()
                    , resolve(std::move(_resolver))
                    , reject(std::move(_rejection))
                { }

                const size_t total;
                size_t resolved;
                bool rejected;
                std::mutex mtx;

                Resolver resolve;
                Rejection reject;
            };

            template<size_t Index, typename T, typename Data>
            static void resolveT(const T& val, Data& data) {
                std::lock_guard<std::mutex> guard(data->mtx);

                if (data->rejected) return;

                // @Check thread-safety of std::get ?
                std::get<Index>(data->results) = val;
                data->resolved++;

                if (data->resolved == data->total) {
                    data->resolve(data->results);
                }
            }

            template<typename Data>
            static void resolveVoid(Data& data) {
                std::lock_guard<std::mutex> guard(data->mtx);

                if (data->rejected) return;

                data->resolved++;

                if (data->resolved == data->total) {
                    data->resolve(data->results);
                }
            }

            template<typename Data>
            static void reject(std::exception_ptr exc, Data& data) {
                std::lock_guard<std::mutex> guard(data->mtx);

                data->rejected = true;
                data->reject(exc);
            }
        };

        struct Any {

            struct Data {
                Data(size_t, Resolver resolver, Rejection rejection)
                    : done(false)
                    , mtx()
                    , resolve(std::move(resolver))
                    , reject(std::move(rejection))
                { }

                bool done;
                std::mutex mtx;

                Resolver resolve;
                Rejection reject;
            };

            template<size_t Index, typename T, typename Data>
            static void resolveT(const T& val, Data& data) {
                std::lock_guard<std::mutex> guard(data->mtx);

                if (data->done) return;

                // Instead of allocating a new core, ideally we could share the same core as
                // the relevant promise but we do not have access to the promise here is so meh
                auto core = std::make_shared<Private::CoreT<T>>();
                core->template construct<T>(val);
                data->resolve(Async::Any(core));

                data->done = true;
            }

            template<typename Data>
            static void resolveVoid(Data& data) {
                std::lock_guard<std::mutex> guard(data->mtx);

                if (data->done) return;

                auto core = std::make_shared<Private::CoreT<void>>();
                data->resolve(Async::Any(core));

                data->done = true;
            }

            template<typename Data>
            static void reject(std::exception_ptr exc, Data& data) {
                std::lock_guard<std::mutex> guard(data->mtx);

                data->done = true;
                data->reject(exc);
            }
        };

        template<typename ContinuationPolicy>
        struct When {
            When(Resolver resolver, Rejection rejection)
                : resolve(std::move(resolver))
                , reject(std::move(rejection))
            { }

            template<typename... Args>
            void operator()(Args&&... args) {
                whenArgs(std::forward<Args>(args)...);
            }

        private:
            template<typename T, size_t Index, typename Data>
            struct WhenContinuation {
                WhenContinuation(Data _data)
                    : data(std::move(_data))
                { }

                void operator()(const T& val) const {
                    ContinuationPolicy::template resolveT<Index>(val, data);
                }

                Data data;
            };

            template<size_t Index, typename Data>
            struct WhenContinuation<void, Index, Data> {
                WhenContinuation(Data _data)
                    : data(std::move(_data))
                { }

                void operator()() const {
                    ContinuationPolicy::resolveVoid(data);
                }

                Data data;

            };

            template<typename T, size_t Index, typename Data>
            WhenContinuation<T, Index, Data>
            makeContinuation(const Data& data) {
                return WhenContinuation<T, Index, Data>(data);
            }

            template<size_t Index, typename Data, typename T>
            void when(const Data& data, Promise<T>& promise) {
                promise.then(makeContinuation<T, Index>(data), [=](std::exception_ptr ptr) {
                    ContinuationPolicy::reject(std::move(ptr), data);
                });
            }

            template<size_t Index, typename Data, typename T>
            void when(const Data& data, T&& arg) {
                typedef typename std::remove_reference<T>::type CleanT;
                auto promise = Promise<CleanT>::resolved(std::forward<T>(arg));
                when<Index>(data, promise);
            }

            template<typename... Args>
            void whenArgs(Args&& ...args) {
                typedef std::tuple<
                            typename detail::RemovePromise<
                                typename std::remove_reference<Args>::type
                            >::Type...
                        > Results;
                /* We need to keep the results alive until the last promise
                 * finishes its execution
                 */

                /* See the trick here ? Basically, we only have access to the real type of the results
                 * in this function. The policy classes do not have access to the full type (std::tuple),
                 * but, instead, take a generic template data type as a parameter. They only need to know
                 * that results is a tuple, they do not need to know the real type of the results.
                 *
                 * This is some sort of compile-time template type-erasing, hue
                 */
                struct Data : public ContinuationPolicy::Data {
                    Data(size_t total, Resolver resolver, Rejection rejection)
                        : ContinuationPolicy::Data(total, std::move(resolver), std::move(rejection))
                    { }

                    Results results;
                };

                auto data = std::make_shared<Data>(sizeof...(Args), std::move(resolve), std::move(reject));
                whenArgs<0>(data, std::forward<Args>(args)...);
            }

            template<size_t Index, typename Data, typename Head,  typename... Rest>
            void whenArgs(const Data& data, Head&& head, Rest&& ...rest) {
                when<Index>(data, std::forward<Head>(head));
                whenArgs<Index + 1>(data, std::forward<Rest>(rest)...);
            }

            template<size_t Index, typename Data, typename Head>
            void whenArgs(const Data& data, Head&& head) {
                when<Index>(data, std::forward<Head>(head));
            }

            Resolver resolve;
            Rejection reject;
        };

        template<
                 typename T,
                 typename Results
                >
        struct WhenAllRange
        {
            WhenAllRange(Resolver _resolve, Rejection _reject)
                : resolve(std::move(_resolve))
                , reject(std::move(_reject))
            { }

            template<typename Iterator>
            void operator()(Iterator first, Iterator last)
            {
                auto data = std::make_shared<DataT<T>>(
                   static_cast<size_t>(std::distance(first, last)),
                   std::move(resolve),
                   std::move(reject)
                );

                size_t index = 0;
                for (auto it = first; it != last; ++it) {

                    WhenContinuation<T> cont(data, index);

                    it->then(std::move(cont), [=](std::exception_ptr ptr) {
                        std::lock_guard<std::mutex> guard(data->mtx);

                        if (data->rejected) return;

                        data->rejected = true;
                        data->reject(std::move(ptr));
                    });

                    ++index;
                }
            }

        private:
            struct Data
            {
                Data(size_t _total, Resolver _resolver, Rejection _rejection)
                    : total(_total)
                    , resolved(0)
                    , rejected(false)
                    , mtx()
                    , resolve(std::move(_resolver))
                    , reject(std::move(_rejection))
                { }

                const size_t total;
                size_t resolved;
                bool rejected;
                std::mutex mtx;

                Resolver resolve;
                Rejection reject;
            };

            /* Ok so apparently I can not fully specialize a template structure
             * here, so you know what, compiler ? Take that Dummy type and leave
             * me alone
             */
            template<typename ValueType, typename Dummy = void> struct DataT : public Data {
                DataT(size_t total, Resolver resolver, Rejection rejection)
                    : Data(total, std::move(resolver), std::move(rejection))
                {
                    results.resize(total);
                }

                Results results;
            };

            /* For a vector of void promises, we do not have any results, that's
             * why we need a distinct specialization for the void case
             */
            template<typename Dummy> struct DataT<void, Dummy> : public Data {
                DataT(size_t total, Resolver resolver, Rejection rejection)
                    : Data(total, std::move(resolver), std::move(rejection))
                {
                }
            };

            template<typename ValueType, typename Dummy = void>
            struct WhenContinuation
            {
                using D = std::shared_ptr<DataT<ValueType>>;

                WhenContinuation(const D& _data, size_t _index)
                    : data(_data)
                    , index(_index)
                { }

                void operator()(const ValueType& val) const
                {
                    std::lock_guard<std::mutex> guard(data->mtx);

                    if (data->rejected) return;

                    data->results[index] = val;
                    data->resolved++;
                    if (data->resolved == data->total)
                    {
                        data->resolve(data->results);
                    }
                }

                D data;
                size_t index;
            };

            template<typename Dummy>
            struct WhenContinuation<void, Dummy>
            {
                using D = std::shared_ptr<DataT<void>>;

                WhenContinuation(const D& _data, size_t)
                    : data(_data)
                { }

                void operator()() const
                {
                    std::lock_guard<std::mutex> guard(data->mtx);

                    if (data->rejected) return;

                    data->resolved++;
                    if (data->resolved == data->total)
                    {
                        data->resolve();
                    }
                }

                D data;
            };

            Resolver resolve;
            Rejection reject;
        };

    }

    template<
        typename... Args,
        typename Results =
        std::tuple<
              typename detail::RemovePromise<
                  typename std::remove_reference<Args>::type
              >::Type...
        >
    >
    Promise<Results> whenAll(Args&& ...args) {
        // As ugly as it looks, this is needed to bypass a bug of gcc < 4.9
        // whereby template parameters pack inside a lambda expression are not
        // captured correctly and can not be expanded inside the lambda.
        Resolver* resolve;
        Rejection* reject;

        Promise<Results> promise([&](Resolver& resolver, Rejection& rejection) {
            resolve = &resolver;
            reject = &rejection;
        });

        Impl::When<Impl::All> impl(std::move(*resolve), std::move(*reject));
        // So we capture everything we need inside the lambda and then call the
        // implementation and expand the parameters pack here
        impl(std::forward<Args>(args)...);

        return promise;
    }

    template<typename... Args>
    Promise<Any> whenAny(Args&& ...args) {
        // Same trick as above;
        Resolver* resolve;
        Rejection* reject;

        Promise<Any> promise([&](Resolver& resolver, Rejection& rejection) {
            resolve = &resolver;
            reject = &rejection;
        });

        Impl::When<Impl::Any> impl(std::move(*resolve), std::move(*reject));
        impl(std::forward<Args>(args)...);
        return promise;
    }

    template<
        typename Iterator,
        typename ValueType
            = typename detail::RemovePromise<
                  typename std::iterator_traits<Iterator>::value_type
              >::Type,
        typename Results
            = typename std::conditional<
                        std::is_same<void, ValueType>::value,
                    void, std::vector<ValueType>
              >::type
    >
    Promise<Results> whenAll(Iterator first, Iterator last) {
        /* @Security, assert that last >= first */

        return Promise<Results>([=](Resolver& resolve, Rejection& rejection) {

            Impl::WhenAllRange<ValueType, Results> impl(
                std::move(resolve), std::move(rejection));

            impl(first, last);

        });
    }

} // namespace Async
} // namespace Pistache
